/* Necessary includes for device drivers */
#include <linux/init.h>
#include <linux/module.h>

/* printk() */
#include <linux/kernel.h>

 /* kmalloc() */
#include <linux/slab.h>

/* File system operations */
#include <linux/fs.h>
#include <linux/proc_fs.h>

/*Error Codes*/
#include <linux/errno.h>

/*For the size_t*/
#include <linux/types.h>

 /* O_ACCMODE */
#include <linux/fcntl.h>

/* cli(), *_flags */
#include <asm/system.h>

 /* copy_from/to_user */
#include <asm/uaccess.h>


/*
 * make clean
 * make all or make default
 * sudo insmod memory.ko
 * modinfo memory.ko
 * rmmod memory
 */

/*Error string messages*/
#define ERR_MAJOR_NUMBER "<1>memory: cannot obtain major number %d\n"

/*Driver Messages*/
#define DRIVER_STATUS_MEM_INIT_SUCCESS "<1>Inserting memory module\n"
#define DRIVER_STATUS_MEM_REMOVE_SUCCESS "<1>Removing memory module\n"

/*Driver name*/
#define DRIVER_NAME "memory"

/*Strings about the driver*/
#define DRIVER_AUTHOR "GeKa <ThisIsFake@gmail.com>"
#define DRIVER_LICENSE "Dual BSD/GPL"
#define DRIVER_DESC "A complete Char Driver"

/*Driver Module information*/
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_LICENSE(DRIVER_LICENSE);
MODULE_DESCRIPTION(DRIVER_DESC);

/* Declaration of memoryinitial.c functions */
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);

/* Structure that declares the usual file */
/* access functions */
struct file_operations memory_fops = {
  .read = &memory_read,
  .write = &memory_write,
  .open = &memory_open,
  .release = &memory_release
};


/* Declaration of the init and exit functions */
module_init(memory_init);
module_exit(memory_exit);

/* Global variables of the driver */
/* Major number */
int memory_major = 60;
/* Buffer to store data */
char *memory_buffer;

int memory_init(void) {
  int result = 0;

  /* Registering device */
  result = register_chrdev(memory_major, DRIVER_NAME, &memory_fops);
  if (result < 0) {
    printk(ERR_MAJOR_NUMBER, memory_major);
    return result;
  }

  /* Allocating memory for the buffer */
  memory_buffer = kmalloc(1, GFP_KERNEL); 
  if (!memory_buffer) { 
    result = -ENOMEM;
    memory_exit();
    return result;
  } 
  memset(memory_buffer, 0, 1);

  printk(DRIVER_STATUS_MEM_INIT_SUCCESS);
  return 0;
}

void memory_exit(void) {
  /* Freeing the major number */
  unregister_chrdev(memory_major, DRIVER_NAME);

  /* Freeing buffer memory */
  if (memory_buffer) {
    kfree(memory_buffer);
  }

  printk(DRIVER_STATUS_MEM_REMOVE_SUCCESS);

}

int memory_open(struct inode *inode, struct file *filp) {

  /* Success */
  return 0;
}


ssize_t memory_read(struct file *filp, char *buf, 
                    size_t count, loff_t *f_pos) { 
 
  /* Transfering data to user space */ 
  copy_to_user(buf,memory_buffer,1);

  /* Changing reading position as best suits */ 
  if (*f_pos == 0) { 
    *f_pos+=1; 
    return 1; 
  } else { 
    return 0; 
  }
}

ssize_t memory_write( struct file *filp, const char *buf,
                      size_t count, loff_t *f_pos) {

  char *tmp;

  tmp=((char*)buf)+count-1;
  copy_from_user(memory_buffer,tmp,1);
  return 1;
}


int memory_release(struct inode *inode, struct file *filp) {
 
  /* Success */
  return 0;
}

